4985 2024-07-31 2024-12-14

Spring Cache 是 Spring 框架中的一个缓存机制,它通过一系列注解(@Cacheable@CachePut@CacheEvict@Caching 等)来实现对缓存的操作和管理。

最近对Spring Cache这一块有点感兴趣,稍稍花点时间从源码级别来稍微总结下(本文基于Spring Boot 2.7.8)。

一、@EnableCaching

Spring Cache的入口就是注解 @EnableCaching,这个是我们切入点。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 这里引入了前置依赖类
// 这里换成不同的导入类就可以实现不同的功能
// 比如下文的 AsyncConfigurationSelector、TransactionManagementConfigurationSelector
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {

    // true-基于cglib的继承机制动态代理,false-基于jdk的接口机制动态代理
	boolean proxyTargetClass() default false;
	
    // 默认为代理模式,同一子类调用代理不会生效
	AdviceMode mode() default AdviceMode.PROXY;

    // 动态代理的顺序
	int order() default Ordered.LOWEST_PRECEDENCE;
}

1、@Import

先看下 @Import,简单理解就是一个可以批量导入未被 @ComponentScans 注解扫描到的非用户Spring组件Bean。

一般来说需要与 @Configuration 配合使用,声明该类是一个Spring系统配置类。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {
	// 需要注入的配置类
	Class<?>[] value();
}

2、@ImportSelector

再来看下 @ImportSelector@AdviceModeImportSelector,这两个是类是 @EnableXXX 具体配置类的父类。

// 以下均为通过 @Import 注解导入,所以说这是Spring Boot新功能模块的引入标准方式
// @EnableCaching 配置类
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {
}

// @EnableAsync 配置类
public class AsyncConfigurationSelector extends AdviceModeImportSelector<EnableAsync> {
}

// @EnableTransactionManagement 配置类
public class TransactionManagementConfigurationSelector extends AdviceModeImportSelector<EnableTransactionManagement> {
}

// 其中 AdviceModeImportSelector implements ImportSelector
public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {
}

简单来说,**@ImportSelector **这个注解是用来批量导入某些系统配置类。

public interface ImportSelector {

    // 根据特定注解,返回一个包含了类全限定名的数组,这些类会注入到Spring容器当中
	String[] selectImports(AnnotationMetadata importingClassMetadata);

	// 用户排除某些非必要类
	@Nullable
	default Predicate<String> getExclusionFilter() {
		return null;
	}

@AdviceModeImportSelector 这个类是用来实现 @EnableAsync、@EnableTransactionManagement、@EnableCaching 等注解的基类,具体的子类可以根据配置参数,选择不同的配置类来实现相应功能。

public abstract class AdviceModeImportSelector<A extends Annotation> implements ImportSelector {

	public static final String DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME = "mode";

	protected String getAdviceModeAttributeName() {
		return DEFAULT_ADVICE_MODE_ATTRIBUTE_NAME;
	}

	@Override
	public final String[] selectImports(AnnotationMetadata importingClassMetadata) {
		// 省略部分代码
	}

	@Nullable
	protected abstract String[] selectImports(AdviceMode adviceMode);
}

3、缓存配置选择器

// @Import(CachingConfigurationSelector.class)
public class CachingConfigurationSelector extends AdviceModeImportSelector<EnableCaching> {

	// 返回特定的干活类,用来导入spring
	@Override
	public String[] selectImports(AdviceMode adviceMode) {
		switch (adviceMode) {
			case PROXY:
                // 走此处逻辑
				return getProxyImports();
			case ASPECTJ:
				return getAspectJImports();
			default:
				return null;
		}
	}

	// 真正干活的类是 AutoProxyRegistrar、ProxyCachingConfiguration
	private String[] getProxyImports() {
		List<String> result = new ArrayList<>(3);
        // AOP支持
		result.add(AutoProxyRegistrar.class.getName());
        // Cache支持
		result.add(ProxyCachingConfiguration.class.getName());
		if (jsr107Present && jcacheImplPresent) {
			result.add(PROXY_JCACHE_CONFIGURATION_CLASS);
		}
		return StringUtils.toStringArray(result);
	}
}

4、注入时机

通过我们前面对于Spring源码的阅读,我们应该还有印象,在BeanFactory初始化后 invokeBeanFactoryPostProcessors 方法这一环节,Spring内部框架会有一个专门的类 ConfigurationClassPostProcessor 负责扫描 @Configuration 并且触发bean初始化。

Spring Boot各模块功能bean就是在此时进行自动装配的,这里不再多做赘述。

@EnableCaching 注解讲解到这里,@EnableAsync、@EnableTransactionManagement 具体实现原理类似。接下来我们看下具体干活的类。

二、相关配置类

与Spring Cache相关类是 AutoProxyRegistrarProxyCachingConfiguration。其中 AutoProxyRegistrar 是配合@Configuration做动态代理的(注意@Configuration与@Component的区别),偏辅助模块。

1、AutoProxyRegistrar

public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
	// 简单一点总结,就是让 @Configuration 功能运行正常,重点关注引入目标类的两个属性
    // proxyTargetClass-是否需要代理类、mode-代理模式,动态或静态代理
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		boolean candidateFound = false;
		Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
		for (String annType : annTypes) {
			AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
			if (candidate == null) {
				continue;
			}
			Object mode = candidate.get("mode");
			Object proxyTargetClass = candidate.get("proxyTargetClass");
			if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
					Boolean.class == proxyTargetClass.getClass()) {
				candidateFound = true;
				if (mode == AdviceMode.PROXY) {
					AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
					if ((Boolean) proxyTargetClass) {
						AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
						return;
					}
				}
			}
		}
	}

}

接下来我们重点看下缓存相关模块类。

2、ProxyCachingConfiguration

代理配置总入口,重点看下 ProxyCachingConfiguration 类,如下

// 不为配置类下bean单独做代理,功能上等同于 @Component
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class ProxyCachingConfiguration extends AbstractCachingConfiguration {

    // 简单点说就是cache增强类,下面两个bean都是服务这个bean的
    // 施加增强的入口bean
	@Bean(name = CacheManagementConfigUtils.CACHE_ADVISOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public BeanFactoryCacheOperationSourceAdvisor cacheAdvisor(
			CacheOperationSource cacheOperationSource, CacheInterceptor cacheInterceptor) {
		// 配置增强器
		BeanFactoryCacheOperationSourceAdvisor advisor = new BeanFactoryCacheOperationSourceAdvisor();
		advisor.setCacheOperationSource(cacheOperationSource);
		advisor.setAdvice(cacheInterceptor);
		if (this.enableCaching != null) {
			advisor.setOrder(this.enableCaching.<Integer>getNumber("order"));
		}
		return advisor;
	}

    // 定义的缓存的操作
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheOperationSource cacheOperationSource() {
		return new AnnotationCacheOperationSource();
	}

    // 拦截缓存,对缓存进行一系列设置
	@Bean
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public CacheInterceptor cacheInterceptor(CacheOperationSource cacheOperationSource) {
		CacheInterceptor interceptor = new CacheInterceptor();
		interceptor.configure(this.errorHandler, this.keyGenerator, this.cacheResolver, this.cacheManager);
		interceptor.setCacheOperationSource(cacheOperationSource);
		return interceptor;
	}
}

3、AbstractCachingConfiguration

再看下 ProxyCachingConfiguration 的父类 AbstractCachingConfiguration

父类定义了一系列用于配置 CacheManager 组件bean方法,我们继承类 CachingConfigurerSupport 类并重写相应方法就行了。

@Configuration(proxyBeanMethods = false)
public abstract class AbstractCachingConfiguration implements ImportAware {

    // 读取用户自定义缓存配置
    // 包括 cacheManage、cacheResolver、keyGenerator、errorHandler
	@Autowired
	void setConfigurers(ObjectProvider<CachingConfigurer> configurers) {
		Supplier<CachingConfigurer> configurer = () -> {
			List<CachingConfigurer> candidates = configurers.stream().collect(Collectors.toList());
			if (CollectionUtils.isEmpty(candidates)) {
				return null;
			}
			if (candidates.size() > 1) {
				throw new IllegalStateException(candidates.size() + " implementations of " +
						"CachingConfigurer were found when only 1 was expected. " +
						"Refactor the configuration such that CachingConfigurer is " +
						"implemented only once or not at all.");
			}
			return candidates.get(0);
		};
		useCachingConfigurer(new CachingConfigurerSupplier(configurer));
	}

	// 快看,我们看到了熟悉的东西
	protected void useCachingConfigurer(CachingConfigurerSupplier cachingConfigurerSupplier) {
		this.cacheManager = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheManager);
		this.cacheResolver = cachingConfigurerSupplier.adapt(CachingConfigurer::cacheResolver);
		this.keyGenerator = cachingConfigurerSupplier.adapt(CachingConfigurer::keyGenerator);
		this.errorHandler = cachingConfigurerSupplier.adapt(CachingConfigurer::errorHandler);
	}
}

4、CacheOperationSource

CacheOperationSource 类定义类Spring Cache有哪些操作注解,这些操作会被Spring正确识别并进行增强处理

// 来自类 CacheOperationSource
public AnnotationCacheOperationSource() {
    this(true);
}

public AnnotationCacheOperationSource(boolean publicMethodsOnly) {
    this.publicMethodsOnly = publicMethodsOnly;
    this.annotationParsers = Collections.singleton(new SpringCacheAnnotationParser());
}

// 注解操作代理类
public class SpringCacheAnnotationParser implements CacheAnnotationParser, Serializable {

	private static final Set<Class<? extends Annotation>> CACHE_OPERATION_ANNOTATIONS = new LinkedHashSet<>(8);

	static {
        // 内置了4种需要处理的注解类
		CACHE_OPERATION_ANNOTATIONS.add(Cacheable.class);
		CACHE_OPERATION_ANNOTATIONS.add(CacheEvict.class);
		CACHE_OPERATION_ANNOTATIONS.add(CachePut.class);
		CACHE_OPERATION_ANNOTATIONS.add(Caching.class);
	}
    
    // 本方法会判断给定目标类是否需要做增强
    // 判断依据为该类的方法是含有特定注解,非类级别
    @Override
	public boolean isCandidateClass(Class<?> targetClass) {
		return AnnotationUtils.isCandidateClass(targetClass, CACHE_OPERATION_ANNOTATIONS);
	}
	// 省略其他相关注解识别判断、注解属性读取设置方法
}

三、整体流程

我们先回忆一下之前AOP相关的内容

1、切点(Pointcut)

每个程序类都拥有多个连接点,如一个拥有两个方法的类,这两个方法都是连接点,即连接点是程序类中客观存在的事物。

AOP通过“切点”定位特定的连接点。连接点相当于数据库中的记录,而切点相当于查询条件。切点和连接点不是一对一的关系,一个切点可以匹配多个连接点。

2、增强器(Advisor)

拦截切点、施加增强,增强器可以有选择性地拦截/增强目标对象中的部分方法,可以理解为增强的集合载体。

3、增强(Advice)

增强是织入到目标类连接点上的一段程序代码,在Spring中,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这便是执行点的方位。结合执行点方位信息和切点信息,我们就可以找到特定的连接点。

一般有 前置增强@Before、环绕增强 @Around、后置增强@AfterReturning/@After。

1、增强器

// 增强器bean,定义拦截规则
public class BeanFactoryCacheOperationSourceAdvisor extends AbstractBeanFactoryPointcutAdvisor {

	@Nullable
	private CacheOperationSource cacheOperationSource;

	private final CacheOperationSourcePointcut pointcut = new CacheOperationSourcePointcut() {
		@Override
		@Nullable
		protected CacheOperationSource getCacheOperationSource() {
			return cacheOperationSource;
		}
	};

	public void setCacheOperationSource(CacheOperationSource cacheOperationSource) {
		this.cacheOperationSource = cacheOperationSource;
	}

	@Override
	public Pointcut getPointcut() {
        // 定义方法拦截点
        // 主要判断方法为上文出现过的 SpringCacheAnnotationParser.isCandidateClass 方法
		return this.pointcut;
	}
}

// 以下3个类均来自Spring Aop模块
public abstract class AbstractBeanFactoryPointcutAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
    // 省略具体方法
}
public abstract class AbstractPointcutAdvisor implements PointcutAdvisor, Ordered, Serializable {
    // 省略具体方法
}
public interface PointcutAdvisor extends Advisor {
	// 获取切入点,一般为类的某一个方法
	Pointcut getPointcut();
}
public interface Pointcut {
	ClassFilter getClassFilter();
	MethodMatcher getMethodMatcher();
}

2、增强时机

具体施加逻辑请读者查看 Spring AOP(二),这里只简单列举下相关类

  1. ApplicationContext刷新后,开始初始化剩下的所有非lazy bean,此步将会触发需要缓存增强的类的bean初始化
  2. 到达getBean方法,目标bean将经历各种 BeanPostProcessor 的层层蹂躏
  3. 最终到达负责AOP相关功能的BeanPostProcessor—AbstractAutoProxyCreator,开始动态代理
  4. AbstractAutoProxyCreator.postProcessAfterInitialization
  5. AbstractAutoProxyCreator.wrapIfNecessary

最终会到达以下关键逻辑

// 来自类 AbstractAutoProxyCreator
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    // 这里我们寻找的bean所对应的增强器
    Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
    if (specificInterceptors != DO_NOT_PROXY) {
        this.advisedBeans.put(cacheKey, Boolean.TRUE);
        // 根据增强器创建动态代理
        Object proxy = createProxy(
                bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
        this.proxyTypes.put(cacheKey, proxy.getClass());
        // 对象已经不是你看到的对象了
        return proxy;
    }

    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return bean;
}

更多AOP相关的逻辑就不做过多深入了,点到为止。

3、增强逻辑

具体的增强逻辑是由类 CacheInterceptor 来负责的。

// 注意它是一个 MethodInterceptor,定义了增强入口
// 具体干活逻辑则交给了父类 CacheAspectSupport
public class CacheInterceptor extends CacheAspectSupport implements MethodInterceptor, Serializable {

	@Override
	@Nullable
	public Object invoke(final MethodInvocation invocation) throws Throwable {
		Method method = invocation.getMethod();

		CacheOperationInvoker aopAllianceInvoker = () -> {
			try {
				return invocation.proceed();
			}
			catch (Throwable ex) {
				throw new CacheOperationInvoker.ThrowableWrapper(ex);
			}
		};

		Object target = invocation.getThis();
		Assert.state(target != null, "Target must not be null");
		try {
            // 具体缓存处理逻辑委托给父类 CacheAspectSupport
			return execute(aopAllianceInvoker, target, method, invocation.getArguments());
		}
		catch (CacheOperationInvoker.ThrowableWrapper th) {
			throw th.getOriginal();
		}
	}
}

四、注解说明

1、@CacheEvict

通常用在删除方法上,用来从缓存中移除对应数据。

参数说明
value/cacheNames指定要清空的缓存名称,可多个
key指定要删除的缓存项的key,可以使用SpEL表达式
allEntries是否清空指定缓存中的所有缓存项,默认为false
beforeInvocation是否在方法执行之前就清空缓存,默认为false,即在方法执行之后清空
condition清除对象的条件,非必需,需使用SpEL表达
keyGenerator用于指定key生成器,非必需
cacheManager用于指定使用哪个缓存管理器,非必需
cacheResolver用于指定使用哪个缓存解析器,非必需

2、@Cacheable

使其能够根据方法的请求参数对其结果进行缓存。在查询时,会先从缓存中取,若不存在才会再发起对数据库的访问。

参数说明
value/cacheNames指定缓存的名称,用于区分不同的缓存集合,可多个
key缓存对象存储在Map集合中的key值,非必需,缺省按照方法的所有参数组合作为key值
condition缓存对象的条件,非必需,需使用SpEL表达式
unless不缓存的数据,可以通过对result进行判断
sync清除对象的条件,非必需,需使用SpEL表达
keyGenerator用于指定key生成器,非必需
cacheManager用于指定使用哪个缓存管理器,非必需
cacheResolver用于指定使用哪个缓存解析器,非必需

3、@CachePut

能够根据方法的请求参数对其结果进行缓存。与@Cacheable不同的是,@CachePut每次都会真实调用函数,所以主要用于数据新增和修改操作上

参数说明
value/cacheNames指定缓存的名称,用于区分不同的缓存集合,可多个
key缓存对象存储在Map集合中的key值,非必需,缺省按照方法的所有参数组合作为key值
condition缓存对象的条件,非必需,需使用SpEL表达式
unless不缓存的数据,可以通过对result进行判断
keyGenerator用于指定key生成器,非必需
cacheManager用于指定使用哪个缓存管理器,非必需
cacheResolver用于指定使用哪个缓存解析器,非必需

4、@Caching

组合多个Cache注解使用,以便在一个方法上同时使用@Cacheable、@CachePut、@CacheEvict等注解

public @interface Caching {

	Cacheable[] cacheable() default {};

	CachePut[] put() default {};

	CacheEvict[] evict() default {}; 
}

5、相关SpEL

Spring Cache提供了一些供我们使用的SpEL上下文数据,下表直接摘自Spring官方文档:

名称位置描述示例
ArgumentNamecontext当前被调用的方法的参数User user -> #user.id
resultcontext方法执行后的返回值 仅当方法执行后的判断有效 如 unless cacheEvict的beforeInvocation=false#result
methodNameroot当前被调用的方法名#root.methodname
methodroot当前被调用的方法#root.method.name
targetroot当前被调用的目标对象实例#root.target
targetClassroot当前被调用的目标对象的类#root.targetClass
argsroot当前被调用的方法的参数列表#root.args[0]
cachesroot当前被调用的方法使用的缓存列表#root.caches[0].name

使用方法参数时,可以直接写成#参数名,也可以写成:#p参数索引,例如#p0表示索引0的参数。

当我们要使用root对象的属性作为key时我们也可以将“#root”省略,因为Spring默认使用的就是root对象的属性

五、处理流程

1、execute

// 来自类 CacheAspectSupport
// 进入此步方法,说明target、method参数已经满足了被增强器增强的前置条件
@Nullable
protected Object execute(CacheOperationInvoker invoker, Object target, Method method, Object[] args) {
    // Check whether aspect is enabled (to cope with cases where the AJ is pulled in automatically)
    // 主要是检查缓存是否存在 CacheManager
    if (this.initialized) {
        // 获取被代理对象的具体类
        Class<?> targetClass = getTargetClass(target);
        CacheOperationSource cacheOperationSource = getCacheOperationSource();
        if (cacheOperationSource != null) {
            // 这里简单的理解,就是寻找对方法需要进行增强的具体配置,例如缓存新增、缓存过期,与注解配置一一对应
            // caches=[tmp_xk_userid_blogs] | key='#userId' | keyGenerator='' | cacheManager='caffeineCacheManager' | cacheResolver='' | condition='' | unless='' | sync='false'
            Collection<CacheOperation> operations = cacheOperationSource.getCacheOperations(method, targetClass);
            if (!CollectionUtils.isEmpty(operations)) {
                // 如果识别到满足特定条件,需要操作,则进行代理增强
                // 这里需要注意下,同一个方法可能被多个注解修饰(类型可相同也可不同),所有会有多个操作
                return execute(invoker, method,
                        new CacheOperationContexts(operations, method, args, target, targetClass));
            }
        }
    }
    // 正常调用
    return invoker.invoke();
}

@Nullable
private Object execute(final CacheOperationInvoker invoker, Method method, CacheOperationContexts contexts) {
    // 同步调用,是否需要显式加锁进行同步调用,由参数 sync 控制,默认false
    // 只有 @Cacheable 注解才有sync属性,且同步模式下 @Cacheable 与其他注解不兼容
    // Special handling of synchronized invocation
    if (contexts.isSynchronized()) {
        CacheOperationContext context = contexts.get(CacheableOperation.class).iterator().next();
        if (isConditionPassing(context, CacheOperationExpressionEvaluator.NO_RESULT)) {
            Object key = generateKey(context, CacheOperationExpressionEvaluator.NO_RESULT);
            Cache cache = context.getCaches().iterator().next();
            try {
                // 这里有同步加锁的操作
                return wrapCacheValue(method, handleSynchronizedGet(invoker, key, cache));
            }
            catch (Cache.ValueRetrievalException ex) {
                // Directly propagate ThrowableWrapper from the invoker,
                // or potentially also an IllegalArgumentException etc.
                ReflectionUtils.rethrowRuntimeException(ex.getCause());
            }
        }
        else {
            // No caching required, just call the underlying method
            return invokeOperation(invoker);
        }
    }

    // 1.如果显式指定方法执行前清除缓存 @CacheEvict,默认是方法执行好清除缓存
    // Process any early evictions
    processCacheEvicts(contexts.get(CacheEvictOperation.class), true,
            CacheOperationExpressionEvaluator.NO_RESULT);

    // 2.再处理 @Cacheable,缓存命中取缓存,不命中则返回方法结果并将该结果放入缓存
    // Check if we have a cached value matching the conditions
    Cache.ValueWrapper cacheHit = findCachedItem(contexts.get(CacheableOperation.class));

    // Collect puts from any @Cacheable miss, if no cached value is found
    List<CachePutRequest> cachePutRequests = new ArrayList<>(1);
    if (cacheHit == null) {
        // 收集需要进行的put操作 @CachePuts,是否满足condition条件
        collectPutRequests(contexts.get(CacheableOperation.class),
                CacheOperationExpressionEvaluator.NO_RESULT, cachePutRequests);
    }

    Object cacheValue;
    Object returnValue;

    // 3.如果缓存命中
    if (cacheHit != null && !hasCachePut(contexts)) {
        // If there are no put requests, just use the cache hit
        cacheValue = cacheHit.get();
        returnValue = wrapCacheValue(method, cacheValue);
    }
    else {
		// 4.反之查询执行代理方法
        // Invoke the method if we don't have a cache hit
        returnValue = invokeOperation(invoker);
        cacheValue = unwrapReturnValue(returnValue);
    }

    // 5.收集 @CachePut,在没有命中缓存的情况下 @Cacheable = @CachePut,是否满足condition条件
    // Collect any explicit @CachePuts
    collectPutRequests(contexts.get(CachePutOperation.class), cacheValue, cachePutRequests);

    // Process any collected put requests, either from @CachePut or a @Cacheable miss
    for (CachePutRequest cachePutRequest : cachePutRequests) {
        // 6.执行真正的set操作,前面只是收集满足条件的指令
        cachePutRequest.apply(cacheValue);
    }

    // 7.先查询数据,再进行删除,默认操作
    // Process any late evictions
    processCacheEvicts(contexts.get(CacheEvictOperation.class), false, cacheValue);
    return returnValue;
}

2、同步处理

// 来自类 CacheAspectSupport
@Nullable
private Object handleSynchronizedGet(CacheOperationInvoker invoker, Object key, Cache cache) {
    InvocationAwareResult invocationResult = new InvocationAwareResult();
    Object result = cache.get(key, () -> {
        invocationResult.invoked = true;
        if (logger.isTraceEnabled()) {
            logger.trace("No cache entry for key '" + key + "' in cache " + cache.getName());
        }
        return unwrapReturnValue(invokeOperation(invoker));
    });
    if (!invocationResult.invoked && logger.isTraceEnabled()) {
        logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
    }
    return result;
}

// hashmap cache,来自类 ConcurrentMapCache
// 底层实现依赖 ConcurrentMap.computeIfAbsent,此方法保证操作线程安全
@Override
@Nullable
public <T> T get(Object key, Callable<T> valueLoader) {
    return (T) fromStoreValue(this.store.computeIfAbsent(key, k -> {
        try {
            return toStoreValue(valueLoader.call());
        }
        catch (Throwable ex) {
            throw new ValueRetrievalException(key, valueLoader, ex);
        }
    }));
}

// spring caffeine,来自类 CaffeineCache
// 大致同 ConcurrentMap,此步会保证操作线程安全
@Nullable
public <T> T get(Object key, Callable<T> valueLoader) {
    return this.fromStoreValue(this.cache.get(key, new CaffeineCache.LoadFunction(valueLoader)));
}

// spring redis,来自类 RedisCache
// 使用 synchronized 关键字进行加锁,确保单机写入串行
@Override
@SuppressWarnings("unchecked")
public <T> T get(Object key, Callable<T> valueLoader) {

    ValueWrapper result = get(key);

    if (result != null) {
        return (T) result.get();
    }
    return getSynchronized(key, valueLoader);
}
@Nullable
@SuppressWarnings("unchecked")
private synchronized <T> T getSynchronized(Object key, Callable<T> valueLoader) {
    ValueWrapper result = get(key);
    if (result != null) {
        return (T) result.get();
    }

    T value;
    try {
        value = valueLoader.call();
    } catch (Exception e) {
        throw new ValueRetrievalException(key, valueLoader, e);
    }
    put(key, value);
    return value;
}

3、数据过期

// 来自类 CacheAspectSupport
// 参数分别为 操作指令Context、是否在方法执行之前调用、方法执行结果
private void processCacheEvicts(
        Collection<CacheOperationContext> contexts, boolean beforeInvocation, @Nullable Object result) {

    for (CacheOperationContext context : contexts) {
        CacheEvictOperation operation = (CacheEvictOperation) context.metadata.operation;
        // 如果是方法之前调用,且condition匹配通过
        // 这里其实有一个点,就是 beforeInvocation = true 的时候,其实啥事也不用做,因为缓存已经更新了
        if (beforeInvocation == operation.isBeforeInvocation() && isConditionPassing(context, result)) {
            performCacheEvict(context, operation, result);
        }
    }
}

private void performCacheEvict(
        CacheOperationContext context, CacheEvictOperation operation, @Nullable Object result) {

    Object key = null;
    // 遍历缓存
    for (Cache cache : context.getCaches()) {
        // 相关代码 CacheEvictOperation.Builder.setCacheWide(cacheEvict.allEntries())
        // 如果是清除所有缓存
        if (operation.isCacheWide()) {
            // 打印日志
            logInvalidating(context, operation, null);
            // 清除数据,默认第二个参数为true,即立即删除-invalidate,而非延时删除-clear
            doClear(cache, operation.isBeforeInvocation());
        }
        else {
            // 清除特定缓存
            if (key == null) {
                // 通过spel生成key,剩下操作大致同上
                key = generateKey(context, result);
            }
            logInvalidating(context, operation, key);
            doEvict(cache, key, operation.isBeforeInvocation());
        }
    }
}

4、数据查找

// 来自类 CacheAspectSupport
@Nullable
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
    Object result = CacheOperationExpressionEvaluator.NO_RESULT;
    for (CacheOperationContext context : contexts) {
        if (isConditionPassing(context, result)) {
            Object key = generateKey(context, result);
            Cache.ValueWrapper cached = findInCaches(context, key);
            if (cached != null) {
                return cached;
            }
            else {
                if (logger.isTraceEnabled()) {
                    logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
                }
            }
        }
    }
    // 未命中则返回null
    return null;
}

@Nullable
private Cache.ValueWrapper findInCaches(CacheOperationContext context, Object key) {
    for (Cache cache : context.getCaches()) {
        Cache.ValueWrapper wrapper = doGet(cache, key);
        // 任意一个缓存命中则直接返回
        if (wrapper != null) {
            if (logger.isTraceEnabled()) {
                logger.trace("Cache entry for key '" + key + "' found in cache '" + cache.getName() + "'");
            }
            return wrapper;
        }
    }
    return null;
}

5、数据新增

// 来自类 CacheAspectSupport.CachePutRequest
public void apply(@Nullable Object result) {
    if (this.context.canPutToCache(result)) {
        // 每个缓存都会进行存储
        for (Cache cache : this.context.getCaches()) {
            doPut(cache, this.key, result);
        }
    }
}

// unless 字段专门用于对结果处理,是否缓存本次方法执行结果
// 因为前面已经处理过condition了,这里只需要考虑unless就行了
protected boolean canPutToCache(@Nullable Object value) {
    String unless = "";
    if (this.metadata.operation instanceof CacheableOperation) {
        unless = ((CacheableOperation) this.metadata.operation).getUnless();
    }
    else if (this.metadata.operation instanceof CachePutOperation) {
        unless = ((CachePutOperation) this.metadata.operation).getUnless();
    }
    if (StringUtils.hasText(unless)) {
        EvaluationContext evaluationContext = createEvaluationContext(value);
        return !evaluator.unless(unless, this.metadata.methodKey, evaluationContext);
    }
    return true;
}

六、测试代码

场景较多,读者可以自行测试,这里不做过多展开了。

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import org.slf4j.LoggerFactory;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

/**
 * vm参数启用assert:-ea
 */
public class CacheTest {

    public static void main(String[] args) {
        // 获取logback的Logger对象,root是根级别
        Logger logger = (Logger) LoggerFactory.getLogger("org.springframework.cache");
        // 设置日志级别
        logger.setLevel(Level.TRACE);

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(CacheTest.class, CacheConfig.class);

        CacheTest bean = context.getBean(CacheTest.class);
        // 缓存初步使用
        String r1 = bean.testGet1(1);
        System.out.println(r1);
        assert r1.equals(bean.testGet1(1));
        assert r1.equals(bean.testGet1(1));

        // 不缓存空数据
        String r2 = bean.testGet2(2);
        assert StrUtil.isNotEmpty(r2);
        assert StrUtil.isEmpty(bean.testGet2(3));
        assert r2.equals(bean.testGet2(2));
        System.out.println("所有测试通过");

        // 测试数据清除
        String r3 = bean.testGet3(2);
        assert !r2.equals(r3);
        r2 = bean.testGet2(2);
        assert r2.equals(bean.testGet2(2));
    }

    @EnableCaching
    static class CacheConfig extends CachingConfigurerSupport {
        // 可以配置多个,也可以自定义逻辑
        @Bean
        @Override
        public CacheManager cacheManager() {
            return new ConcurrentMapCacheManager("no_name");
        }
    }

    @Cacheable(cacheNames = "no_name", key = "'user1_' + #userId")
    public String testGet1(Integer userId) {
        return "userId=" + userId + "_" + RandomUtil.randomString(4);
    }

    @Cacheable(cacheNames = "no_name", key = "'user2_' + #userId", unless = "#result == null || #result == ''")
    public String testGet2(Integer userId) {
        return userId % 2 == 0 ? RandomUtil.randomString(4) : null;
    }

    @CacheEvict(cacheNames = "no_name", key = "'user2_' + #userId")
    public String testGet3(Integer userId) {
        return userId % 2 == 0 ? RandomUtil.randomString(4) : null;
    }

    @CacheEvict(cacheNames = "no_name", key = "'user2' + #userId", condition = "#p0 % 3 == 0")
    public String testGet4(Integer userId) {
        return userId % 2 == 0 ? RandomUtil.randomString(4) : null;
    }
}

打完收工,又是一种豁然开朗的感觉,共勉!

总访问次数: 11次, 一般般帅 创建于 2024-07-31, 最后更新于 2024-12-14

进大厂! 欢迎关注微信公众号,第一时间掌握最新动态!